﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
using System.ComponentModel;

using System.Drawing;

namespace LowLevelGraphics.CustomControls
{
    /// <summary>
    /// Selectable Picturebox
    /// </summary>
    public class SelectablePictureBox : ScrollableControl, ISupportInitialize
    {
        protected Image m_Image = null;
        protected float m_Zoom = 1.0f;
        protected InterpolationMode m_InterpolationMode = InterpolationMode.High;
        protected Color m_FocusColor = Color.White;

        /// <summary>
        /// Initializes a new instance of the <see cref="SelectablePictureBox"/> class.
        /// </summary>
        public SelectablePictureBox()
        {
            this.DoubleBuffered = true;
            this.AutoScroll = true;
            SetStyle(ControlStyles.Selectable, true);
            this.TabStop = true;
        }

        /// <summary>
        /// Gets or sets the interpolation mode.
        /// </summary>
        /// <value>The interpolation mode.</value>
        [Category("Appearance")]
        [Description("The interpolation mode used to smooth the drawing")]
        public InterpolationMode InterpolationMode
        {
            get { return m_InterpolationMode; }
            set { m_InterpolationMode = value; }
        }

        /// <summary>
        /// Gets or sets the image.
        /// </summary>
        /// <value>The image.</value>
        [Category("Appearance")]
        [Description("The image to be displayed")]
        public Image Image
        {
            get { return m_Image; }
            set
            {
                m_Image = value;
                UpdateScaleFactor();
                Invalidate();
            }
        }

        /// <summary>
        /// Gets or sets the color of the focus.
        /// </summary>
        /// <value>The color of the focus.</value>
        [Category("Appearance")]
        [Description("The color of the focus rectangle if focused")]
        public Color FocusColor
        {
            get { return m_FocusColor; }
            set { m_FocusColor = value; }
        }

        [Category("Appearance")]
        [Description("The zoom factor. Less than 1 to reduce, more than 1 to magnify")]
        public float Zoom
        {
            get { return m_Zoom; }
            set
            {
                if ((value < 0) || (value < 1E-05))
                {
                    value = 1E-05F;
                }
                m_Zoom = value;
                UpdateScaleFactor();
                Invalidate();
            }
        }

        /// <summary>
        /// Gets or sets the background image displayed in the control.
        /// </summary>
        /// <value></value>
        /// <returns>
        /// An <see cref="T:System.Drawing.Image"/> that represents the image to display in the background of the control.
        /// </returns>
        /// <PermissionSet>
        /// 	<IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        /// </PermissionSet>
        public override Image BackgroundImage
        {
            get {  return m_Image; }
            set
            {
                this.m_Image = value;
                Invalidate();
            }
        }

        /// <summary>
        /// Updates the scale factor.
        /// </summary>
        protected void UpdateScaleFactor()
        {
            if (m_Image == null)
            {
                AutoScrollMargin = this.Size;
            }
            else
            {
                AutoScrollMinSize = new Size((int)(m_Image.Width * m_Zoom + 0.5f), (int)(m_Image.Height * Zoom + 0.5f));
            }
        }

        /// <summary>
        /// Zeichnet den Hintergrund des Steuerelements.
        /// </summary>
        /// <param name="e"><see cref="T:System.Windows.Forms.PaintEventArgs"/> mit den Ereignisdaten.</param>
        protected override void OnPaintBackground(PaintEventArgs e)
        {
            //clear implementation: normally there would be a draw routine this overridance clears it
        }

        /// <summary>
        /// Raises the <see cref="E:MouseDown"/> event.
        /// </summary>
        /// <param name="args">The <see cref="System.Windows.Forms.MouseEventArgs"/> instance containing the event data.</param>
        protected override void OnMouseDown(MouseEventArgs args)
        {
            base.OnMouseDown(args);
            this.Focus();
        }

        /// <summary>
        /// Raises the <see cref="E:KeyPress"/> event.
        /// </summary>
        /// <param name="args">The <see cref="System.Windows.Forms.KeyPressEventArgs"/> instance containing the event data.</param>
        protected override void OnKeyPress(KeyPressEventArgs args)
        {
            if (args.KeyChar == '\r')
            {
                OnClick(EventArgs.Empty);
            }
            else
            {
                base.OnKeyPress(args);
            }
        }

        /// <summary>
        /// Raises the <see cref="E:Enter"/> event.
        /// </summary>
        /// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected override void OnEnter(EventArgs args)
        {
            base.OnEnter(args);
            this.Invalidate();
        }

        /// <summary>
        /// Raises the <see cref="E:System.Windows.Forms.Control.Leave"/> event.
        /// </summary>
        /// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param>
        protected override void OnLeave(EventArgs e)
        {
            base.OnLeave(e);
            this.Invalidate();
        }

        /// <summary>
        /// Raises the <see cref="E:System.Windows.Forms.Control.Paint"/> event.
        /// </summary>
        /// <param name="e">A <see cref="T:System.Windows.Forms.PaintEventArgs"/> that contains the event data.</param>
        protected override void OnPaint(PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            if (m_Image == null)
            {
                OnPaintBackground(e);
                return;
            }

            switch (this.BackgroundImageLayout)
            {
                case ImageLayout.Center:
                    Size sizeCenter = m_Image.Size;
                    Point ptCenter = Center(sizeCenter);
                    g.DrawImage(m_Image, ptCenter.X, ptCenter.Y, sizeCenter.Width, sizeCenter.Height);
                    break;

                case ImageLayout.Stretch:
                    g.DrawImage(m_Image, new Rectangle(0, 0, this.ClientRectangle.Width, this.ClientRectangle.Height), 0, 0, m_Image.Width, m_Image.Height, GraphicsUnit.Pixel);
                    break;

                case ImageLayout.Tile:
                    TextureBrush texture = new TextureBrush(m_Image);
                    texture.WrapMode = WrapMode.Tile;
                    g.FillRectangle(texture,
                        new RectangleF(0, 0, this.ClientRectangle.Width, this.ClientRectangle.Height));

                    break;

                case ImageLayout.Zoom:
                    Size sizeAspectRatio = AspectRatio();
                    Point ptCenterZoom = Center(sizeAspectRatio);
                    g.DrawImage(m_Image, ptCenterZoom.X, ptCenterZoom.Y, sizeAspectRatio.Width, sizeAspectRatio.Height);
                    break;

                case ImageLayout.None:
                    ApplyZoom(e);
                    g.DrawImage(m_Image, new Rectangle(0, 0, m_Image.Width, m_Image.Height), 0, 0, m_Image.Width, m_Image.Height, GraphicsUnit.Pixel);
                    break;
            }
            base.OnPaint(e);

            if (this.Focused
                && this.CanFocus)
            {
                DrawFocusRectangle(g);
            }
        }

        /// <summary>
        /// Draws the focus rectangle.
        /// </summary>
        /// <param name="g">The g.</param>
        private void DrawFocusRectangle(Graphics g)
        {
            Brush focusBrush = new SolidBrush(m_FocusColor);
            Pen focusPen = new Pen(focusBrush, g.DpiX / 12);
            g.DrawRectangle(focusPen,
            this.ClientRectangle);
            focusPen.Dispose();
            focusBrush.Dispose();
        }

        /// <summary>
        /// Centers the specified size.
        /// </summary>
        /// <param name="size">The size.</param>
        /// <returns></returns>
        private Point Center(Size size)
        {
            int x = this.ClientRectangle.Width / 2 - size.Width / 2;
            int y = this.ClientRectangle.Height / 2 - size.Height / 2;
            return new Point(x, y);
        }

        /// <summary>
        /// Takes Care of Aspect ratio.
        /// </summary>
        /// <returns></returns>
        private Size AspectRatio()
        {
            float nPercent = 0;
            float nPercentW = 0;
            float nPercentH = 0;

            int nSourceWidth = m_Image.Width;
            int nSourceHeight = m_Image.Height;

            nPercentW = ((float)Width / (float)nSourceWidth);
            nPercentH = ((float)Height / (float)nSourceHeight);
            int nDestinationX = 0;
            int nDestinationY = 0;
            if (nPercentH < nPercentW)
            {
                nPercent = nPercentH;
                nDestinationX = System.Convert.ToInt16((Width -
                              (nSourceWidth * nPercent)) / 2);
            }
            else
            {
                nPercent = nPercentW;
                nDestinationY = System.Convert.ToInt16((Height -
                              (nSourceHeight * nPercent)) / 2);
            }

            int nDestinationWidth = (int)(nSourceWidth * nPercent);
            int nDestinationHeight = (int)(nSourceHeight * nPercent);
            return new Size(nDestinationWidth, nDestinationHeight);
        }

        /// <summary>
        /// Applies the zoom.
        /// </summary>
        /// <param name="e">The <see cref="System.Windows.Forms.PaintEventArgs"/> instance containing the event data.</param>
        private void ApplyZoom(PaintEventArgs e)
        {
            Matrix mx = new Matrix(m_Zoom, 0, 0, m_Zoom, 0, 0);
            mx.Translate(this.AutoScrollPosition.X / m_Zoom, this.AutoScrollPosition.Y / Zoom);
            e.Graphics.Transform = mx;
            e.Graphics.InterpolationMode = m_InterpolationMode;
        }

        #region ISupportInitialize Members

        /// <summary>
        /// Signals the object that initialization is starting.
        /// </summary>
        public void BeginInit()
        {
        }

        /// <summary>
        /// Signals the object that initialization is complete.
        /// </summary>
        public void EndInit()
        {
        }

        #endregion
    }
}
